﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Expand.Collections.Generic;

namespace Expand.Patterns.Command
{
    public class History
    {
        private SingleLinkedList<ICommand> _UndoStack = new SingleLinkedList<ICommand>();

        private SingleLinkedList<ICommand> _RedoStack = new SingleLinkedList<ICommand>();

        public void Do(ICommand command)
        {
            if (_CompoundDoCollection == null)
            {
                command.Execute();
                Done(command);
            }
            else
            {
                _CompoundDoCollection.AddLast(command);
            }
        }

        public void Done(ICommand command)
        {
            _UndoStack.AddFirst(command);
            _RedoStack.Clear();
            OnCommandDone(this, new HistoryEventArgs(_UndoStack.Count, _RedoStack.Count));
        }

        public bool Undoing { get; private set; }

        public bool Redoing { get; private set; }

        public int TotalUndoCommands { get { return _UndoStack.Count; } }

        public int TotalRedoCommands { get { return _RedoStack.Count; } }

        public bool CanBeUndone { get { return _UndoStack.Count > 0; } }

        public bool CanBeRedone { get { return _RedoStack.Count > 0; } }

        private LinkedList<ICommand> _CompoundDoCollection = null;

        /// <summary>
        /// Use History.Do to add commands to compound do
        /// </summary>
        public void BeginCompoundDo()
        {
            if (_CompoundDoCollection != null) throw new InvalidOperationException("Doing already a CompoundDo");

            _CompoundDoCollection = new LinkedList<ICommand>();
        }

        public void EndCompundDo()
        {
            EndCompundDo(false);
        }

        public void EndCompundDo(bool commandAlreadyDone)
        {
            if (_CompoundDoCollection == null) throw new InvalidOperationException("Not building any CompoundDo");

            LinkedList<ICommand> tmp = _CompoundDoCollection;
            _CompoundDoCollection = null;
            if (commandAlreadyDone)
                Done(new CompoundCommand(tmp));
            else
                Do(new CompoundCommand(tmp));
        }

        public void Undo()
        {
            if (_UndoStack.Count < 1) throw new InvalidOperationException("Undo stack is empty");

            Undoing = true;
            ICommand command = _UndoStack.First;
            OnCommandUndoing(command, EventArgs.Empty);
            _UndoStack.RemoveFirst();
            command.UnExecute();
            _RedoStack.AddFirst(command);
            Undoing = false;
            OnCommandUndone(this, new HistoryEventArgs(_UndoStack.Count, _RedoStack.Count));
        }

        public void Redo()
        {
            if (_RedoStack.Count < 1) throw new InvalidOperationException("Redo stack is empty");

            Redoing = true;
            ICommand command = _RedoStack.First;
            OnCommandRedoing(command, EventArgs.Empty);
            _RedoStack.RemoveFirst();
            command.Execute();
            _UndoStack.AddFirst(command);
            Redoing = false;
            OnCommandRedone(this, new HistoryEventArgs(_UndoStack.Count, _RedoStack.Count));
        }

        public void Clear()
        {
            _UndoStack.Clear();
            _RedoStack.Clear();
        }

        protected void OnCommandDone(object sender, HistoryEventArgs e) { if (CommandDone != null) CommandDone(sender, e); }
        protected void OnCommandUndone(object sender, HistoryEventArgs e) { if (CommandUndone != null) CommandUndone(sender, e); }
        protected void OnCommandRedone(object sender, HistoryEventArgs e) { if (CommandRedone != null) CommandRedone(sender, e); }
        protected void OnCommandUndoing(object sender, EventArgs e) { if (CommandUndoing != null) CommandUndoing(sender, e); }
        protected void OnCommandRedoing(object sender, EventArgs e) { if (CommandRedoing != null) CommandRedoing(sender, e); }

        public event EventHandler<HistoryEventArgs> CommandDone;
        public event EventHandler<HistoryEventArgs> CommandUndone;
        public event EventHandler<HistoryEventArgs> CommandRedone;

        public event EventHandler CommandUndoing;
        public event EventHandler CommandRedoing;
    }
}
